package net.gdface.db;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import gu.sql2java.Constant;
import gu.sql2java.Managers;
import gu.sql2java.ScriptRunner;
import net.gdface.service.client.LocalApp;
import net.gdface.utils.ConfigUtils;
import net.gdface.utils.InterfaceContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static net.gdface.db.DaoManagement.DAO;
import static net.gdface.service.client.LocalApp.CONFIG;

/**
 * 数据库管理对象<br>
 * 单实例对象
 * @author guyadong
 *
 */
public class  DatabaseManager{
	private static final Logger logger = LoggerFactory.getLogger(DatabaseManager.class);
	public static final String TASK_DB = "mydb";
	// /////////////////////配置变量,在loadParametersFromProperties中初始化///////////////////
	private boolean runInMemory;
	private boolean cleanTask;
	private int dbBackupIntervalSeconds;

	// ////////////////////////////////////////////////
	private final File root;
	private final File taskdb;

	private long lastBackupBeginTimeMills = 0L;
	private long lastBackupEndTimeMills = 0L;
	/**
	 * 数据库持久化侦听器容器
	 */
	public final InterfaceContainer<BackupHook> backuphooks = new InterfaceContainer<BackupHook>(BackupHook.class);
	private final String jdbcUrl;
	private static volatile DatabaseManager singleton=null;

	/**
	 * @return instance
	 */
	public static DatabaseManager getInstance() {
		if (null == singleton){
			throw new IllegalArgumentException(
					"instance没有初始化, 请先执行 create(File root, boolean reSearch, boolean runInMemory, M memoryDatabase) 创建实例");
		}
		return singleton;
	}
	public static DatabaseManager createSingleton(File root, boolean cleanTask, boolean runInMemory) throws Exception {
		// double check
		if (null == singleton){
			synchronized (DatabaseManager.class) {
				if (null == singleton){
					singleton = new DatabaseManager(root, cleanTask, runInMemory).init();		
				}
			}			
		}
		return singleton;
	}
	
	private DatabaseManager(File root, boolean cleaTask, boolean runInMemory) {
		loadParametersFromProperties();
		this.root=root;
		this.cleanTask=cleaTask;
		this.runInMemory=runInMemory;
		if (!(this.root.exists() && this.root.isDirectory()))
			throw new IllegalArgumentException(new FileNotFoundException(this.root.getAbsolutePath()));
		this.taskdb = new File(this.root.getAbsolutePath() + "/" + TASK_DB);
		this.jdbcUrl = createJDBCUrl(runInMemory, taskdb);
	}

	/**
	 * 从配置文件中加载参数
	 */
	private void loadParametersFromProperties() {
		CONFIG.setPrefix(DatabaseManager.class.getName() + ".");
		this.dbBackupIntervalSeconds = CONFIG.getPropertyBaseType("dbBackupIntervalSeconds", 300);
	}

	/**
	 * 初始化数据库
	 * 
	 * @return 当前对象
	 * @throws Exception
	 */
	public DatabaseManager init() throws Exception {
		logger.info("derby location(本地数据库位置)=[{}]", this.taskdb.getAbsolutePath());	
		this.modifyDbProperties();
		if (!this.taskdb.exists()) {
			// 如果没找到数据库文件，就新建数据库
			logger.info(String.format("Initializing database %s...", TASK_DB));
			new ScriptRunner(false, true).runScript(Constant.class.getResourceAsStream("/create_table.sql"));
		} else if (!this.taskdb.isDirectory()) {
			throw new IllegalArgumentException(String.format("%s IS NOT a derby database",
					this.taskdb.getAbsolutePath()));
		} else if (this.cleanTask) {
			// 删除所有记录，重新搜索文件
			DAO.daoDeleteTaskAll();
		}
		return this;
	}

	/**
	 * 将数据库回写到磁盘指定的目录
	 * 
	 * @param path
	 */
	private synchronized void persistDB(File path) {
		lastBackupBeginTimeMills = System.currentTimeMillis();
		try {
			this.backuphooks.container.onPersistDB();
			if (this.runInMemory) {
				String backupStr = "CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE(?)";
				logger.info(String.format("wirte in-memory database to(回写数据库到磁盘) %s", path.getAbsolutePath()));
				Managers.getSqlRunner().runSql(backupStr,
						new Object[] { path.getAbsolutePath() });
				logger.info("write back success(回写结束)");
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			lastBackupEndTimeMills = System.currentTimeMillis();
		}
	}
	
	private static final Properties dbprops = getDbProps();
	private static final Properties getDbProps(){
        String envVar="U4FDB_DATABASE_CONFIG";
        String confFolder="conf";
        String propertiesFile = "u4fdb_database.properties";
        Properties props = ConfigUtils.loadAllProperties(propertiesFile, confFolder, envVar, LocalApp.class, false);
        return ConfigUtils.loadPropertiesInUserHome(props, ".u4fdb/" + propertiesFile);
	}
	
	private static String createJDBCUrl(boolean runInMemory, File taskdb) {
		String create = null;
		String jdbcurl = null;
		if (runInMemory) {
			create = taskdb.exists() && taskdb.isDirectory() ? "restoreFrom="
					+ taskdb.getAbsolutePath() : "create=true";
			jdbcurl = String.format("jdbc:derby:memory:%s;%s", TASK_DB, create);
		} else {
			create = taskdb.exists() && taskdb.isDirectory() ? "" : ";create=true";
			jdbcurl = String.format("jdbc:derby:%s%s", taskdb.getAbsolutePath(), create);
		}
		return jdbcurl;
	}
	
	private void modifyDbProperties() throws IOException {
		logger.info("jdbc.url= [{}]", jdbcUrl);
		dbprops.setProperty("jdbc.url", jdbcUrl);
		// 加载新的数据库连接参数
		Managers.injectProperties(dbprops,null);		
	}

	public void onFinished() {
		persistDB(this.root);
	}

	/**
	 * 启动备份线程 定期将内存中的数据回写到数据库进行备份 当derby数据库在内存运行时，还要进行derby数据库备份
	 * 
	 * @return
	 * @see #persistDB(File)
	 */
	public Thread startDbBackuper() {
		final long intervalMills = TimeUnit.MILLISECONDS.convert(dbBackupIntervalSeconds, TimeUnit.SECONDS);
		Thread t = new Thread("backup(备份线程)") {
			@Override
			public void run() {
				logger.info("backup thread start 备份线程开始,备份间隔={}s(秒)", dbBackupIntervalSeconds);
				try {
					//通过线程中断状态来决定是否结束循环
					while (!this.isInterrupted()) {
						Thread.sleep(intervalMills);
						// 通过lastBackupBeginTimeMills是否小于 lastBackupEndTimeMills,来判断止次备份是否结束
						if (lastBackupBeginTimeMills < lastBackupEndTimeMills) {// 正常备份
							if ((System.currentTimeMillis() - lastBackupEndTimeMills) >= intervalMills)
								persistDB(root);
						} else if (0 == lastBackupBeginTimeMills && lastBackupBeginTimeMills == lastBackupEndTimeMills)// 第一次备份
							persistDB(root);
						else
							// 上次备份还没结束
							logger.warn("备份时间间隔太短，上次还备份没结束，放弃本次备份");
					}
				} catch (InterruptedException e) {
					this.interrupt();
				} finally {
					logger.info("{} end (结束)",Thread.currentThread().getName());
				}
			}
		};
		//t.setDaemon(true);
		t.start();
		return t;
	}

}